package com.github.llyb120.service;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.BCUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import com.antherd.smcrypto.sm2.Keypair;
import com.antherd.smcrypto.sm2.Sm2;
import com.antherd.smcrypto.sm3.Sm3;
import com.github.llyb120.crypt.Util;
import com.github.llyb120.ctrl.TestController;
import com.github.llyb120.json.Arr;
import com.github.llyb120.json.Json;
import com.github.llyb120.json.Obj;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.Synchronized;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.signers.PlainDSAEncoding;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

import java.io.IOException;
import java.security.KeyPair;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.github.llyb120.json.Json.*;

@ServerEndpoint(value = "/api/socket")
@Component
public class WebSocketService {
    @Autowired
    TestController testController;

    private static Keypair keypair = Sm2.generateKeyPairHex();
//    static String privateKey = keypair.getPrivateKey(); // 公钥
//    static String publicKey = keypair.getPublicKey(); // 私钥
    private static Keypair keypair2 = Sm2.generateKeyPairHex();

    private static Map<String, List> db = new ConcurrentHashMap<>();
    static {
        db.put("user", a(
            o("username", "admin", "password", Sm3.sm3("admin"))
        ));
        db.put("setting", a(
            o("key", "系统名", "value", Sm2.doEncrypt("密码演示系统",keypair.getPublicKey())),
            o("key", "系统说明", "value", Sm2.doEncrypt("仅仅是一个测试演示的系统",keypair.getPublicKey()))
        ));
    }

    private static Map<Session, Boolean> logonMap = new WeakHashMap<>();

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session) {
//        pair = SM2EncDecUtils.generateKeyPair();
            send(session, "RESP_PUBLIC_KEY", null, o(
                            "pub", keypair.getPublicKey(),//HexUtil.encodeHexStr(sm2.getPublicKey().getEncoded()),//(pair.getPublicKey().getEncoded()),
                            "pri", keypair2.getPrivateKey()//Util.byteToHex(BCUtil.encodeECPrivateKey(sm2.getPrivateKey())),
                    ).toString(),
                false
            );
//        pair.getPriHexInSoft()
//        Obj obj = (Obj) testController.generatePair();
        //发送公钥
//        System.out.println(123);
//        onlineCount.incrementAndGet(); // 在线数加1
//        log.info("有新连接加入：{}，当前在线人数为：{}", session.getId(), onlineCount.get());
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session) {
//        onlineCount.decrementAndGet(); // 在线数减1
//        log.info("有一连接关闭：{}，当前在线人数为：{}", session.getId(), onlineCount.get());
    }


    @OnMessage
    public void onMessage(byte[] messages, Session session) {
//        byte[] res = sm2.decrypt(messages, KeyType.PrivateKey);
        System.out.println(messages);
//        System.out.println(res);
//        log.info("服务端收到客户端[{}]的消息:{}", session.getId(), message);
//        this.sendMessage("Hello, " + message, session);
    }

    @OnMessage
    public void onMessage(String message, Session session){
        try {
            Obj obj = Json.parse(message);
            Request request = obj.to(Request.class);
            if (!request.getEvent().equals("REQ_LOGIN")) {
                if(!logonMap.containsKey(session)){
                    send(session, "RESP_AUTH_FAILED", null, null, true);
                    return;
                }
            }
            String de = Sm2.doDecrypt(request.getData(), keypair.getPrivateKey());
            switch (request.getEvent()){
                case "REQ_LOGIN":
                    Obj data = Json.parse(de);
                    login(session, request.getRequestId(), data.ss("username"), data.ss("password"));
                    break;
                case "REQ_DB_LIST":
                    dbList(session, request.getRequestId());
                    break;
                case "REQ_LOAD_SETTING":
                    loadSetting(session, request.getRequestId());
                    break;
                case "REQ_SAVE_SETTING":
                    Arr arr = Json.parse(de);
                    saveSetting(session, request.getRequestId(), arr);
                    break;
            }
//            System.out.println(data);
//            send(session, "test", request.getRequestId(), o("foo", "bar"), true);
//            session.getBasicRemote().sendText(o(
//                    "data", "foobar",
//                   "responseId", request.getRequestId()
//            ).toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @SneakyThrows
    @Synchronized
    void saveSetting(Session session, String reqId, Arr<?> setting){
        db.put("setting", a($expand, setting.os().map(e -> {
            return o("key", e.ss("key"), "value", Sm2.doEncrypt(e.ss("value"), keypair.getPublicKey()));
        }).toArray()));
        send(session, "RESP_SAVE_SETTING", reqId, o("code", 0), true);
    }

    @SneakyThrows
    @Synchronized
    void loadSetting(Session session, String reqId){
        Arr<?> setting = (Arr<?>) db.get("setting");
        send(
            session,
            "RESP_LOAD_SETTING",
            reqId,
            setting.os()
                .map(e -> {
                    return o("key", e.ss("key"), "value", Sm2.doDecrypt(e.ss("value"), keypair.getPrivateKey()));
                })
                .toArray(),
            true
        );
    }

    @Synchronized
    void dbList(Session session, String reqId){
       send(session, "RESP_DB_LIST", reqId, db, true);
    }
    /**
     * 登录借口
     *
     * @param username
     * @param password
     */
    @Synchronized
    void login(Session session, String reqId, String username, String password){
        Arr<?> list = (Arr) db.get("user");
        String pwd = Sm3.sm3(password);
        if(list.os()
            .anyMatch(e -> e.ss("username").equals(username) && e.ss("password").equals(pwd))){
            logonMap.put(session, true);
            send(session, "RESP_LOGIN", reqId, o("code", 0, "msg", "授权成功"), true);
        } else {
            send(session, "RESP_LOGIN", reqId, o("code", -1, "msg", "授权失败"), true);
        }
    }

    void send(Session session, String event, String respId, Object data, boolean crypt){
        if (data == null) {
            data = o();
        }
        send(session, event, respId, Json.stringify(data), crypt);
    }
    void send(Session session, String event, String respId, String data, boolean crypt){
        try {
            session.getBasicRemote().sendText(
                o(
                    "event", event,
                    "responseId", respId,
                    "data", crypt? Sm2.doEncrypt(data, keypair2.getPublicKey()) : data
                ).toString()
            );
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Data
    public static class Request{
        String event;
        String data;
        String requestId  ;
    }
}
